﻿using System;
using System.IO;
using System.Text;
using System.Collections.Generic;
using System.Xml.Serialization;

using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Content.Pipeline;

using Tomahawk.Runtime.Rendering.Materials;
using System.Xml;
using System.Globalization;

namespace Tomahawk.Processors
{
    #region XML Material Importer
    /// <summary>
    /// Creates a SASMaterialCollection from an XML serialization (for debugging purposes)
    /// </summary>
    [ContentImporter(".xml", DefaultProcessor = "MaterialProcessor",
          DisplayName = "XML Material Importer - Tomahawk Engine")]
    public class XMLMaterialImporter : ContentImporter<SASMaterialCollection>
    {
        public override SASMaterialCollection Import(string filename, ContentImporterContext context)
        {
            SASMaterialCollection output = null;

            try
            {
                using (FileStream fs = new FileStream(filename, FileMode.Open, FileAccess.Read))
                {
                    XmlSerializer x = new XmlSerializer(typeof(SASMaterialCollection));
                    output = (SASMaterialCollection)x.Deserialize(fs);
                }
            }
            catch (InvalidContentException inner)
            {
                throw inner;
            }
            catch (Exception inner)
            {
                throw new InvalidContentException("Unknown error while importing the XML content file", inner);
            }

            return output;
        }
    }
    #endregion

    #region DAE Material Importer
    /// <summary>
    /// Creates a SASMaterialCollection from an DAE file. This files are created in the FX Composer editor
    /// that can be found here http://www.developer.nvidia.com/
    /// </summary>
    [ContentImporter(".dae", DefaultProcessor = "MaterialProcessor",
          DisplayName = "DAE Material Importer - Tomahawk Engine")]
    public class DAEMaterialImporter : ContentImporter<SASMaterialCollection>
    {
        #region DAEImage
        public class DAEImage
        {
            public string id;
            public string name;
            public string path;
        }
        #endregion

        #region DAEEffect
        public class DAEEffect
        {
            public string id;
            public string name;
            public string path;
        }
        #endregion

        #region DAEParameter
        public class DAEParameter
        {
            public string type;
            public string value;
        }
        #endregion

        #region DAEMaterial
        public class DAEMaterial
        {
            public string id;
            public string name;
            public string effect;
            public Dictionary<string, DAEParameter> parameters = new Dictionary<string,DAEParameter>();
        }
        #endregion

        protected Dictionary<string, DAEImage> imageTable = new Dictionary<string, DAEImage>();
        protected Dictionary<string, DAEEffect> effectTable = new Dictionary<string, DAEEffect>();
        protected Dictionary<string, DAEMaterial> materialTable = new Dictionary<string, DAEMaterial>();

        #region Read Images
        protected bool ReadImages(XmlReader reader)
        {
            if (reader.Name == "library_images" || reader.ReadToFollowing("library_images"))
            {
                XmlReader libraryReader = reader.ReadSubtree();
                while (!libraryReader.EOF)
                {
                    if (libraryReader.Name == "image" || libraryReader.ReadToFollowing("image"))
                    {
                        XmlReader imageReader = libraryReader.ReadSubtree();
                        string id = libraryReader["id"];
                        string name = libraryReader["name"];
                        if (imageReader.Name == "init_from" || imageReader.ReadToFollowing("init_from"))
                        {
                            string path = imageReader.ReadElementContentAsString();
                            DAEImage daeImage = new DAEImage();
                            daeImage.id = id;
                            daeImage.name = name;
                            daeImage.path = path;

                            if (imageTable.ContainsKey(id.ToUpper()))
                            {
                                throw new InvalidContentException(string.Format("The image id {0} is duplicated", id));
                            }

                            imageTable.Add(id.ToUpper(), daeImage);
                            imageReader.ReadEndElement();
                        }
                        imageReader.Close();
                        libraryReader.ReadEndElement();
                    }
                }
                libraryReader.Close();
                reader.ReadEndElement();
            }

            return true;
        }
        #endregion

        #region Read Effects
        protected bool ReadEffects(XmlReader reader)
        {
            if (reader.Name == "library_effects" || reader.ReadToFollowing("library_effects"))
            {
                XmlReader libraryReader = reader.ReadSubtree();
                while (!libraryReader.EOF)
                {
                    if (libraryReader.Name == "effect" || libraryReader.ReadToFollowing("effect"))
                    {
                        XmlReader effectReader = libraryReader.ReadSubtree();
                        string id = libraryReader["id"];
                        string name = libraryReader["name"];
                        string path = null;
                        while (!effectReader.EOF && path == null)
                        {
                            effectReader.ReadToFollowing("import");
                            string profile = effectReader["profile"];
                            if (profile == "fx")
                            {
                                path = effectReader["url"];
                            }
                        }
                        if (path != null)
                        {
                            DAEEffect daeEffect = new DAEEffect();
                            daeEffect.id = id;
                            daeEffect.name = name;
                            daeEffect.path = path;

                            if (effectTable.ContainsKey(id.ToUpper()))
                            {
                                throw new InvalidContentException(string.Format("The effect id {0} is duplicated", id));
                            }

                            effectTable.Add(id.ToUpper(), daeEffect);
                        }
                        effectReader.Close();
                        libraryReader.ReadEndElement();
                    }
                }
                libraryReader.Close();
                reader.ReadEndElement();
            }

            return true;
        }
        #endregion

        #region Read Materials
        protected bool ReadMaterials(XmlReader reader)
        {
            if (reader.Name == "library_materials" || reader.ReadToFollowing("library_materials"))
            {
                XmlReader libraryReader = reader.ReadSubtree();
                while (!libraryReader.EOF)
                {
                    if (libraryReader.Name == "material" || libraryReader.ReadToFollowing("material"))
                    {
                        XmlReader materialReader = libraryReader.ReadSubtree();
                        DAEMaterial material = new DAEMaterial();
                        material.id = libraryReader["id"];
                        material.name = libraryReader["name"];

                        if (materialTable.ContainsKey(material.name.ToUpper()))
                        {
                            throw new InvalidContentException(string.Format("The material id {0} is duplicated", material.name));
                        }

                        materialTable.Add(material.name.ToUpper(), material);

                        if (materialReader.Name == "instance_effect" || materialReader.ReadToFollowing("instance_effect"))
                        {
                            material.effect = materialReader["url"].TrimStart(new char[] { '#' });
                            XmlReader paramReader = materialReader.ReadSubtree();
                            while (!paramReader.EOF)
                            {
                                if (paramReader.Name == "setparam" || paramReader.ReadToFollowing("setparam"))
                                {
                                    DAEParameter parameter = new DAEParameter();

                                    XmlReader valueReader = paramReader.ReadSubtree();
                                    valueReader.ReadToFollowing("setparam");
                                    string paramId = paramReader["ref"];

                                    valueReader.Read();
                                    string paramType = valueReader.Name;
                                    switch (paramType)
                                    {
                                        case "surface":
                                            {
                                                parameter.type = paramType;
                                                valueReader.ReadToFollowing("init_from");
                                                parameter.value = !valueReader.EOF ? valueReader.ReadElementString() : "TARGET";
                                            } break;
                                        default:
                                            {
                                                parameter.type = paramType;
                                                parameter.value = valueReader.ReadElementString();
                                            } break;
                                    }

                                    if (material.parameters.ContainsKey(paramId.ToUpper()))
                                    {
                                        throw new InvalidContentException(string.Format("The parameter id {0} in material {1} is duplicated", paramId, material));
                                    }

                                    material.parameters.Add(paramId.ToUpper(), parameter);
                                    
                                    valueReader.Close();
                                    paramReader.ReadEndElement();
                                }
                            }
                            paramReader.Close();
                            materialReader.ReadEndElement();
                        }
                        materialReader.Close();
                        libraryReader.ReadEndElement();
                    }
                }
                libraryReader.Close();
                reader.ReadEndElement();
            }

            return true;
        }
        #endregion

        protected SASMaterialTemplateParameter BuildSASParameter(DAEParameter param)
        {
            NumberFormatInfo nfi = new CultureInfo("en-US", false).NumberFormat;

            switch (param.type)
            {
                case "bool": return new SASMaterialTemplateBoolParameter() { value = System.Boolean.Parse(param.value) };
                case "int": return new SASMaterialTemplateIntParameter() { value = System.Int32.Parse(param.value, nfi) };
                case "float": return new SASMaterialTemplateFloatParameter() { value = System.Single.Parse(param.value, nfi) };
                case "surface":
                    {
                        DAEImage daeImage = null;
                        if (param.value == "TARGET")
                        {
                            return null;
                        }
                        else if (imageTable.TryGetValue(param.value.ToUpper(), out daeImage))
                        {
                            string imagePath = Path.GetDirectoryName(daeImage.path);
                            string imageFile = Path.GetFileNameWithoutExtension(daeImage.path);
                            string imageName = Path.Combine(imagePath, imageFile);

                            return new SASMaterialTemplateTextureParameter() { value = imageName };
                        }

                        throw new InvalidContentException(string.Format("Invalid image id {0}", param.value));
                    }
                case "float2":
                    {
                        Vector4 vector = new Vector4();
                        string[] tokens = param.value.Split(new char[] { ' ' });
                        if (tokens.Length == 2)
                        {
                            vector.X = System.Single.Parse(tokens[0], nfi);
                            vector.Y = System.Single.Parse(tokens[1], nfi);
                        }
                        return new SASMaterialTemplateVectorParameter() { value = vector };
                    }
                case "float3":
                    {
                        Vector4 vector = new Vector4();
                        string[] tokens = param.value.Split(new char[] { ' ' });
                        if (tokens.Length == 3)
                        {
                            vector.X = System.Single.Parse(tokens[0], nfi);
                            vector.Y = System.Single.Parse(tokens[1], nfi);
                            vector.Z = System.Single.Parse(tokens[2], nfi);
                        }
                        return new SASMaterialTemplateVectorParameter() { value = vector };
                    }
                case "float4":
                    {
                        Vector4 vector = new Vector4();
                        string[] tokens = param.value.Split(new char[] { ' ' });
                        if (tokens.Length == 4)
                        {
                            vector.X = System.Single.Parse(tokens[0], nfi);
                            vector.Y = System.Single.Parse(tokens[1], nfi);
                            vector.Z = System.Single.Parse(tokens[2], nfi);
                            vector.W = System.Single.Parse(tokens[3], nfi);
                        }
                        return new SASMaterialTemplateVectorParameter() { value = vector };
                    }
                case "float4x4": 
                case "surfaceDEPTH": return null;
                default: throw new InvalidContentException(string.Format("Parameter type {0} not implemented", param.type));
            }
        }

        public override SASMaterialCollection Import(string filename, ContentImporterContext context)
        {
            SASMaterialCollection output = null;

            try
            {
                using (FileStream fs = new FileStream(filename, FileMode.Open, FileAccess.Read))
                {
                    XmlReaderSettings settings = new XmlReaderSettings();
                    settings.IgnoreWhitespace = true;

                    XmlReader reader = XmlReader.Create(fs, settings);
                    ReadImages(reader);
                    ReadEffects(reader);
                    ReadMaterials(reader);

                    output = new SASMaterialCollection();

                    foreach (KeyValuePair<string, DAEMaterial> daeMaterial in materialTable)
                    {
                        SASMaterialTemplate matTemplate = new SASMaterialTemplate();
                        matTemplate.name = daeMaterial.Key;

                        DAEEffect daeEffect = null;
                        if (effectTable.TryGetValue(daeMaterial.Value.effect.ToUpper(), out daeEffect))
                        {
                            string effectPath = Path.GetDirectoryName(daeEffect.path);
                            string effectFile = Path.GetFileNameWithoutExtension(daeEffect.path);
                            matTemplate.effectName = Path.Combine(effectPath, effectFile);

                            foreach (KeyValuePair<string, DAEParameter> daeParam in daeMaterial.Value.parameters)
                            {
                                SASMaterialTemplateParameter sasParam = BuildSASParameter(daeParam.Value);
                                if (sasParam != null)
                                {
                                    sasParam.name = daeParam.Key;
                                    matTemplate.parameters.Add(sasParam);
                                }

                            }

                            output.materialTemplates.Add(daeMaterial.Value.name.ToUpper(), matTemplate);
                        }
                    }
                }
            }
            catch (InvalidContentException inner)
            {
                throw inner;
            }
            catch (Exception inner)
            {
                throw new InvalidContentException("Unknown error while importing the DAE content file", inner);
            }

            return output;
        }
    }
    #endregion
}